﻿using System;
using System.Diagnostics.Contracts;
using System.Reflection;
using Beaker.Reflection;

namespace Beaker
{
	/// <summary>
	/// Extensions to and/or for the <see cref="Maybe{T}"/> type.
	/// </summary>
	public static class Maybe
	{
		/// <summary>
		/// Create a maybe of the given type.
		/// </summary>
		/// <param name="type">The type of the maybe to create.</param>
		/// <returns>The maybe of the requested type, without any value.</returns>
		public static IMaybe Create(Type type)
		{
			Type maybeType = typeof(Maybe<>).MakeGenericType(type);
			return (IMaybe)Activator.CreateInstance(maybeType);
		}

		/// <summary>
		/// Create a maybe of the given type and the provided value.
		/// </summary>
		/// <param name="type">The type of the maybe to create.</param>
		/// <param name="value">The value assign to the maybe.</param>
		/// <returns>The maybe of the requested type and with the provided value.</returns>
		/// <remarks><c>null</c> is a valid value. If you want to create a maybe without a value use the overloaded Create method
		/// without a value argument, or switch to <see cref="CreateWithNullConversion"/> to convert null to a valueless maybe.</remarks>
		public static IMaybe Create(Type type, object value)
		{
			Type maybeType = typeof(Maybe<>).MakeGenericType(type);
			return (IMaybe)Activator.CreateInstance(maybeType, value);
		}

		/// <summary>
		/// Create a maybe of the given type and the provided value, converting a null to a maybe without value.
		/// </summary>
		/// <param name="type">The type of the maybe to create.</param>
		/// <param name="value">The value to assign to the maybe. If the value is <c>null</c> the maybe will have no value.</param>
		/// <returns>The maybe of the requested type and with the provided value.</returns>
		public static IMaybe CreateWithNullConversion(Type type, object value)
		{
			if (value == null)
				return Create(type);
			return Create(type, value);
		}

		/// <summary>
		/// Create a maybe of the type that the type argument provides.
		/// </summary>
		/// <typeparam name="TValue">The type of the maybe to create.</typeparam>
		/// <returns>The maybe of the provided type, but without any value.</returns>
		public static Maybe<TValue> Create<TValue>()
		{
			return new Maybe<TValue>();
		}

		/// <summary>
		/// Create a maybe of the type that matches the value.
		/// </summary>
		/// <typeparam name="TValue">The type of the maybe to create.</typeparam>
		/// <param name="value">The value to assign to the value.</param>
		/// <returns>The maybe with the provided value.</returns>
		public static Maybe<TValue> Create<TValue>(TValue value)
		{
			return new Maybe<TValue>(value);
		}

		/// <summary>
		/// Create a maybe of the type that matches the value.
		/// </summary>
		/// <typeparam name="TValue">The type of the maybe to create.</typeparam>
		/// <param name="value">The value to assign to the value.</param>
		/// <returns>The maybe with the provided value.</returns>
		/// <remarks><c>null</c> will be converted to an valueless maybe. </remarks>
		public static Maybe<TValue> CreateWithNullConversion<TValue>(TValue value)
		{
			if (value == null)
				return new Maybe<TValue>();
			return new Maybe<TValue>(value);
		}

		/// <summary>
		/// Functionaly the same as CreateWithNullConversion, but with a name to help with From Select pattern
		/// </summary>
		/// <typeparam name="TValue">The type of the maybe to create.</typeparam>
		/// <param name="value">The value to assign to the value.</param>
		/// <returns>The maybe with the provided value.</returns>
		/// <remarks><c>null</c> will be converted to an valueless maybe. </remarks>
		public static Maybe<TValue> From<TValue>(TValue value)
			where TValue : class
		{
			if (value == null)
				return new Maybe<TValue>();

			return new Maybe<TValue>(value);
		}

		/// <summary>
		/// Get the underlying type of the Maybe 
		/// </summary>
		/// <param name="type">The type of the maybe to get the underlying type from.</param>
		/// <returns>The underlying type of the maybe.</returns>
		/// <exception cref="ArgumentNullException">Throw when the provided argument is null.</exception>
		/// <exception cref="ArgumentException">Throw when the provided argument is not a <see cref="Maybe{TUnderlying}"/> type.</exception>
		public static Type GetUnderlyingType(Type type)
		{
			Contract.Requires<ArgumentNullException>(type != null);
			Contract.Requires<ArgumentException>(type.GetTypeInfo().IsGenericType);
			Contract.Requires<ArgumentException>(type.GetTypeInfo().GetGenericTypeDefinition() == typeof(Maybe<>));

#if NET4
			return type.GetGenericArguments()[0];
#else
			return type.GenericTypeArguments[0];
#endif
		}
	}
}
